package com.qianxun.framework.common.util;

import com.google.gson.*;
import com.google.gson.internal.ConstructorConstructor;
import com.google.gson.internal.LinkedTreeMap;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;

import java.io.IOException;
import java.lang.reflect.Field;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * gson格式化创建器
 * @author: huwei
 * @date: 2020/1/8 14:53
 * @version: 1.0.0
 */
public class GsonFormatBuilder {
    private GsonFormatBuilder(){}
    /**
     * 默认格式的Gson对象，该Gson对象默认配置：
     * 序列化null值设置、Date日期格式：yyyy-MM-dd HH:mm:ss ，LocalDate日期格式：yyyy-MM-dd ，LocalDateTime日期格式：yyyy-MM-dd HH:mm:ss
     * Json字符串读取转换： 注册自定义TypeAdapter，用于区分整数和浮点数的问题
     * @return
     */
    public static final Gson defaultFormatGson(){
        return DefaultGsonHolder.readGson;
    }
    /**
     * 为指定GsonBuilder注册自定义TypeAdapter用于区分整数和浮点数
     * @param gsonBuilder gson构建器
     * @return
     */
    public static Gson formatGson(GsonBuilder gsonBuilder){
        return registerNumberTypeAdapter(gsonBuilder ,gsonBuilder.create()).create();
    }
    /**
     * 为指定GsonBuilder注册NumberTypeAdapter用于区分整数和浮点数
     * @param gsonBuilder gson构造器
     * @param writeGson 用于序列化的gson
     * @return
     */
    private static GsonBuilder registerNumberTypeAdapter(GsonBuilder gsonBuilder ,Gson writeGson){
        return gsonBuilder.registerTypeAdapterFactory(new TypeAdapterFactory() {
            @Override
            public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
                return new NumberTypeAdapter<T>(gson ,type ,writeGson);
            }
        });
    }
    /**
     * gson 对象持有者
     */
    private static class DefaultGsonHolder {
        /**
         * 解析json字串的Gson对象，该对象将只解析json，并在解析式区分整数和浮点数，以规避在原生gson转换时将整数转换为浮点数的问题
         */
        static final Gson readGson;
        /**
         * 序列化对象的Gson对象，该对象将只对对象进行序列化操作，规避readGson对序列化的重复实现
         */
        static final Gson writeGson;
        static{
            GsonBuilder writeGsonBuilder = new GsonBuilder();
            //格式json序列化格式设置
            writeGsonBuilder.disableHtmlEscaping().serializeNulls().setDateFormat("yyyy-MM-dd HH:mm:ss");
            //注册LocalDateTime格式
            writeGsonBuilder.registerTypeAdapter(LocalDateTime.class, (JsonSerializer<LocalDateTime>) (src, typeOfSrc, context) -> new JsonPrimitive(src.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))));
            //LocalDate
            writeGsonBuilder.registerTypeAdapter(LocalDate.class, (JsonSerializer<LocalDate>) (src, typeOfSrc, context) -> new JsonPrimitive(src.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))));
            writeGson = writeGsonBuilder.create();

            //注册类型工厂，固定返回NumberTypeAdapter ，用于处理整型被转换为浮点型的问题
            readGson = registerNumberTypeAdapter(new GsonBuilder() ,writeGson).create();
        }
    }
    /**
     * 整数和浮点数适配
     * @param <T>
     */
    private static class NumberTypeAdapter<T> extends TypeAdapter {
        private Gson readGson;
        private Gson writeGson;
        private TypeToken<T> type;
        private ConstructorConstructor constructorConstructor;
        NumberTypeAdapter(Gson readGson , TypeToken<T> type ,Gson writeGson){
            this.type = type;
            this.readGson = readGson;
            this.writeGson = writeGson;
            this.constructorConstructor = new ConstructorConstructor(new HashMap<>());
        }
        @Override
        public Object read(JsonReader in) throws IOException {
            Object obj = readInner(in);
            if(Collection.class.isAssignableFrom(type.getRawType()) || Map.class.isAssignableFrom(type.getRawType())){
                //集合与map直接返回
                return obj;
            }
            T instance = constructorConstructor.get(type).construct();
            //将obj中的值赋值给instance中相同字段名，且值类型相同的字段；
            ObjectUtil.switchAllFieldValue(instance ,obj);
            //值检索，检索是否存在未赋值成功的字段，并补偿
            makeUpForField(instance ,ObjectUtil.toMap(obj));
            return ObjectUtil.isEmpty(instance) ? obj : instance;
        }
        /**
         * 对象的序列化操作，将对象转换为json字符串
         * @param out 输出对象
         * @param value 输出数据
         * @throws IOException
         */
        @Override
        public void write(JsonWriter out, Object value) throws IOException {
            writeGson.toJson(value ,value.getClass() ,out);
        }
        /**
         * 读取json信息，并区分浮点数和整数
         * @param in json的读取对象
         * @return
         * @throws IOException
         */
        private Object readInner(JsonReader in) throws IOException {
            JsonToken token = in.peek();
            switch (token) {
                case BEGIN_ARRAY:
                    List<Object> list = new ArrayList<Object>();
                    in.beginArray();
                    while (in.hasNext()) {
                        list.add(read(in));
                    }
                    in.endArray();
                    return list;
                case BEGIN_OBJECT:
                    Map<String, Object> map = new LinkedTreeMap<String, Object>();
                    in.beginObject();
                    while (in.hasNext()) {
                        map.put(in.nextName(), readInner(in));
                    }
                    in.endObject();
                    return map;
                case STRING:
                    return in.nextString();
                case NUMBER:
                    //将其作为一个字符串读取出来
                    String numberStr = in.nextString();
                    //返回的numberStr不会为null
                    if (numberStr.contains(".") || numberStr.contains("e") || numberStr.contains("E")) {
                        return Double.parseDouble(numberStr);
                    }
                    return Long.parseLong(numberStr);
                case BOOLEAN:
                    return in.nextBoolean();
                case NULL:
                    in.nextNull();
                    return null;
                default:
                    throw new IllegalStateException();
            }
        }
        /**
         * 检索给定实例，是否存在为赋值成功的字段，并赋值
         * @param instance 检索对象
         * @param data 字段对应值的map
         */
        private void makeUpForField(Object instance ,Map<String ,Object> data ){
            Map<String,Object> instanceMap = ObjectUtil.toMap(instance);
            List<Field> instanceFieldList = ObjectUtil.getFieldsInFamily(instance);
            instanceMap.forEach((fieldName ,v)->{
                if(ObjectUtil.isNull(v) && data.get(fieldName) != null){
                    // 存在未赋值成功的字段，主要原因：字段类型不同
                    // 如：读取json字符串中的时间类型，该类型为字符串(格式化时间)/long类型(时间戳)，而当前转换类型为java.util.Date
                    Field field = getField(instanceFieldList ,fieldName);
                    setFieldValue(instance ,field ,adaptClassValue(field.getType() ,data.get(fieldName)));
                }
            });
        }
        /**
         * 将指定值适配为指定类对象可接受的值
         * 目前，针对于json转换，只做了日期格式的适配，若对其它类型存在问题，到时候在添加
         * @param cls 类对象
         * @param value 值
         * @return 成功，类对象可接受的值，反之，返回null
         */
        private Object adaptClassValue(Class<?> cls ,Object value){
            //java.util.Date 格式转换
            if(cls.isAssignableFrom(Date.class)){
                if(value.getClass().isAssignableFrom(long.class) || value.getClass().isAssignableFrom(Long.class)){
                    return new Date((long)value);
                }else if(value.getClass().isAssignableFrom(String.class)){
                    //该方式用于获取gson设置的日期格式化样式，若无法获取，请检索Gson该字段是否变更
                    String datePattern = (String)ObjectUtil.getFieldValue(writeGson ,"datePattern");
                    if(!ObjectUtil.isNull(datePattern)) {
                        try {
                            return new SimpleDateFormat(datePattern).parse((String)value);
                        } catch (ParseException e) {
                            //转换失败，不做处理，赋值不成功
                        }
                    }
                }
            }
            return null;
        }
        /**
         * 从列表中获取指定字段名的字段
         * @param fieldList 字段列表
         * @param fieldName 字段名
         * @return
         */
        private Field getField(List<Field> fieldList ,String fieldName){
            for(int i = 0 ;i < fieldList.size() ;++i){
                if(fieldName.equals(fieldList.get(i).getName())){
                    return fieldList.get(i);
                }
            }
            return null;
        }
        /**
         * 为指定字段设置值
         * @param obj 对象
         * @param field 字段
         * @param value 值
         */
        private void setFieldValue(Object obj,Field field ,Object value){
            field.setAccessible(true);
            try {
                field.set(obj, value);
            } catch (IllegalAccessException e) {
            }
        }
    }
}
